﻿namespace DomainServices.Tools
{
    using System;
    using System.CodeDom;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Globalization;
    using System.Linq;
    using System.ServiceModel.DomainServices;
    using System.ServiceModel.DomainServices.Server;

    internal class DomainOperationEntryProxyGenerator : ProxyGenerator
    {
        private DomainServiceDescription _domainServiceDescription;
        private CodeTypeDeclaration _proxyClass;
        private const string QuerySuffix = "Query";

        public DomainOperationEntryProxyGenerator(CodeDomClientCodeGenerator clientProxyGenerator, CodeTypeDeclaration proxyClass, DomainServiceDescription domainServiceDescription) : base(clientProxyGenerator)
        {
            this._proxyClass = proxyClass;
            this._domainServiceDescription = domainServiceDescription;
        }

        private bool CanGenerateDomainOperationEntry(DomainOperationEntry domainOperationEntry)
        {
            string methodName = (domainOperationEntry.Operation == DomainOperation.Query) ? domainOperationEntry.Name : (domainOperationEntry.Name + "Query");
            if (Enumerable.Any<CodeTypeMember>(this._proxyClass.Members.Cast<CodeTypeMember>(), c => c.Name == methodName))
            {
                base.ClientProxyGenerator.LogError(string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.ClientCodeGen_NamingCollision_MemberAlreadyExists, new object[] { this._proxyClass.Name, methodName }));
                return false;
            }
            foreach (DomainOperationParameter parameter in domainOperationEntry.Parameters.ToArray<DomainOperationParameter>())
            {
                Type nonNullableType = System.ServiceModel.DomainServices.TypeUtility.GetNonNullableType(parameter.ParameterType);
                if (nonNullableType.IsEnum)
                {
                    string errorMessage = null;
                    if (!base.ClientProxyGenerator.CanExposeEnumType(nonNullableType, out errorMessage))
                    {
                        base.ClientProxyGenerator.LogError(string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.ClientCodeGen_Domain_Op_Enum_Error, new object[] { domainOperationEntry.Name, this._proxyClass.Name, nonNullableType.FullName, errorMessage }));
                        return false;
                    }
                    base.ClientProxyGenerator.RegisterUseOfEnumType(nonNullableType);
                }
            }
            return true;
        }

        public override void Generate()
        {
            HashSet<Type> set = new HashSet<Type>();
            foreach (DomainOperationEntry entry in from m in this._domainServiceDescription.DomainOperationEntries
                where m.Operation == DomainOperation.Query
                orderby m.Name
                select m)
            {
                System.Func<PropertyDescriptor, bool> func = null;
                Type entityType;
                if (this.CanGenerateDomainOperationEntry(entry))
                {
                    this.GenerateEntityQueryMethod(entry);
                    entityType = System.ServiceModel.DomainServices.TypeUtility.GetElementType(entry.ReturnType);
                    if (!set.Contains(entityType))
                    {
                        set.Add(entityType);
                        if (func == null)
                        {
                            func = p => p.ComponentType != entityType;
                        }
                        bool flag = Enumerable.Any<PropertyDescriptor>(this._domainServiceDescription.GetParentAssociations(entityType), func);
                        Type rootEntityType = this._domainServiceDescription.GetRootEntityType(entityType);
                        if (!flag && (rootEntityType == entityType))
                        {
                            this.GenerateEntitySet(entityType);
                        }
                    }
                }
            }
            foreach (Type type2 in from e in this._domainServiceDescription.EntityTypes
                orderby e.Name
                select e)
            {
                foreach (DomainOperationEntry entry2 in this._domainServiceDescription.GetCustomMethods(type2))
                {
                    if (this.CanGenerateDomainOperationEntry(entry2))
                    {
                        this.GenerateDomainOperationEntry(entry2);
                    }
                }
            }
        }

        private void GenerateDomainOperationEntry(DomainOperationEntry domainMethod)
        {
            CodeMemberMethod method = new CodeMemberMethod {
                Name = domainMethod.Name,
                Attributes = MemberAttributes.Public | MemberAttributes.Final
            };
            List<CodeExpression> list = new List<CodeExpression>();
            DomainOperationParameter[] parameterArray = domainMethod.Parameters.ToArray<DomainOperationParameter>();
            string comment = string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.CodeGen_DomainClient_Custom_Method_Summary_Comment, new object[] { domainMethod.Name, domainMethod.AssociatedType.Name });
            method.Comments.AddRange(CodeGenUtilities.GenerateSummaryCodeComment(comment, base.ClientProxyGenerator.IsCSharp));
            comment = string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.CodeGen_DomainContext_Custom_Method_Entity_Parameter_Comment, new object[] { domainMethod.AssociatedType.Name });
            method.Comments.AddRange(CodeGenUtilities.GenerateParamCodeComment(parameterArray[0].Name, comment, base.ClientProxyGenerator.IsCSharp));
            for (int i = 1; i < parameterArray.Length; i++)
            {
                comment = string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.CodeGen_DomainContext_Custom_Method_Parameter_Comment, new object[] { parameterArray[i].Name });
                method.Comments.AddRange(CodeGenUtilities.GenerateParamCodeComment(parameterArray[i].Name, comment, base.ClientProxyGenerator.IsCSharp));
            }
            for (int j = 0; j < parameterArray.Length; j++)
            {
                DomainOperationParameter parameter = parameterArray[j];
                CodeParameterDeclarationExpression expression = new CodeParameterDeclarationExpression(CodeGenUtilities.GetTypeReference(CodeGenUtilities.TranslateType(parameter.ParameterType), base.ClientProxyGenerator, this._proxyClass), parameter.Name);
                method.Parameters.Add(expression);
                if (j > 0)
                {
                    list.Add(new CodeVariableReferenceExpression(parameter.Name));
                }
            }
            method.Statements.Add(new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression(parameterArray[0].Name), domainMethod.Name, list.ToArray())));
            this._proxyClass.Members.Add(method);
        }

        private void GenerateEntityQueryMethod(DomainOperationEntry domainOperationEntry)
        {
            string str = domainOperationEntry.Name + "Query";
            Type elementType = System.ServiceModel.DomainServices.TypeUtility.GetElementType(domainOperationEntry.ReturnType);
            CodeMemberMethod queryMethod = new CodeMemberMethod {
                Name = str,
                Attributes = MemberAttributes.Public | MemberAttributes.Final,
                ReturnType = CodeGenUtilities.GetTypeReference("System.ServiceModel.DomainServices.Client.EntityQuery", this._domainServiceDescription.DomainServiceType.Namespace, false)
            };
            queryMethod.ReturnType.TypeArguments.Add(CodeGenUtilities.GetTypeReference(elementType.FullName, this._domainServiceDescription.DomainServiceType.Namespace, true));
            DomainOperationParameter[] parameterArray = domainOperationEntry.Parameters.ToArray<DomainOperationParameter>();
            string comment = string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.EntityCodeGen_ConstructorComments_Summary_DomainContext, new object[] { elementType.Name, domainOperationEntry.Name });
            queryMethod.Comments.AddRange(CodeGenUtilities.GenerateSummaryCodeComment(comment, base.ClientProxyGenerator.IsCSharp));
            foreach (DomainOperationParameter parameter in parameterArray)
            {
                comment = string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.CodeGen_Query_Method_Parameter_Comment, new object[] { parameter.Name });
                queryMethod.Comments.AddRange(CodeGenUtilities.GenerateParamCodeComment(parameter.Name, comment, base.ClientProxyGenerator.IsCSharp));
            }
            comment = string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.CodeGen_Query_Method_Returns_Comment, new object[] { domainOperationEntry.AssociatedType.Name });
            queryMethod.Comments.AddRange(CodeGenUtilities.GenerateReturnsCodeComment(comment, base.ClientProxyGenerator.IsCSharp));
            IEnumerable<Attribute> attributes = domainOperationEntry.Attributes.Cast<Attribute>();
            CustomAttributeGenerator.GenerateCustomAttributes(base.ClientProxyGenerator, this._proxyClass, ex => string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.ClientCodeGen_Attribute_ThrewException_CodeMethod, new object[] { ex.Message, queryMethod.Name, this._proxyClass.Name, ex.InnerException.Message }), attributes, queryMethod.CustomAttributes, queryMethod.Comments);
            CodeVariableReferenceExpression targetObject = new CodeVariableReferenceExpression("parameters");
            if (parameterArray.Length > 0)
            {
                CodeTypeReference type = CodeGenUtilities.GetTypeReference(typeof(Dictionary<string, object>), base.ClientProxyGenerator, this._proxyClass);
                CodeVariableDeclarationStatement statement = new CodeVariableDeclarationStatement(type, "parameters", new CodeObjectCreateExpression(type, new CodeExpression[0]));
                queryMethod.Statements.Add(statement);
            }
            foreach (DomainOperationParameter parameter2 in parameterArray)
            {
                CodeParameterDeclarationExpression paramDecl = new CodeParameterDeclarationExpression(CodeGenUtilities.GetTypeReference(CodeGenUtilities.TranslateType(parameter2.ParameterType), base.ClientProxyGenerator, this._proxyClass), parameter2.Name);
                IEnumerable<Attribute> enumerable2 = parameter2.Attributes.Cast<Attribute>();
                string customCommentHeader = string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.ClientCodeGen_Attribute_Parameter_FailedToGenerate, new object[] { parameter2.Name });
                CustomAttributeGenerator.GenerateCustomAttributes(base.ClientProxyGenerator, this._proxyClass, ex => string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.ClientCodeGen_Attribute_ThrewException_CodeMethodParameter, new object[] { ex.Message, paramDecl.Name, queryMethod.Name, this._proxyClass.Name, ex.InnerException.Message }), enumerable2, paramDecl.CustomAttributes, queryMethod.Comments, customCommentHeader);
                queryMethod.Parameters.Add(paramDecl);
                queryMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(targetObject, "Add"), new CodeExpression[] { new CodePrimitiveExpression(parameter2.Name), new CodeVariableReferenceExpression(parameter2.Name) }));
            }
            CodeExpressionCollection source = new CodeExpressionCollection();
            source.Add(new CodePrimitiveExpression(domainOperationEntry.Name));
            if (parameterArray.Length > 0)
            {
                source.Add(targetObject);
            }
            else
            {
                source.Add(new CodePrimitiveExpression(null));
            }
            QueryAttribute operationAttribute = (QueryAttribute) domainOperationEntry.OperationAttribute;
            source.Add(new CodePrimitiveExpression(operationAttribute.HasSideEffects));
            source.Add(new CodePrimitiveExpression(operationAttribute.IsComposable));
            CodeExpression expression2 = new CodePrimitiveExpression(null);
            if (parameterArray.Length > 0)
            {
                expression2 = targetObject;
            }
            CodeExpressionStatement statement2 = new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "ValidateMethod", new CodeExpression[] { new CodePrimitiveExpression(str), expression2 }));
            queryMethod.Statements.Add(statement2);
            CodeTypeReference reference2 = CodeGenUtilities.GetTypeReference(elementType.FullName, this._domainServiceDescription.DomainServiceType.Namespace, true);
            CodeMethodReferenceExpression method = new CodeMethodReferenceExpression(new CodeBaseReferenceExpression(), "CreateQuery", new CodeTypeReference[] { reference2 });
            CodeMethodReturnStatement statement3 = new CodeMethodReturnStatement(new CodeMethodInvokeExpression(method, source.Cast<CodeExpression>().ToArray<CodeExpression>()));
            queryMethod.Statements.Add(statement3);
            this._proxyClass.Members.Add(queryMethod);
        }

        private void GenerateEntitySet(Type entityType)
        {
            string propertyName = Naming.MakePluralName(entityType.Name);
            if (Enumerable.Any<CodeTypeMember>(this._proxyClass.Members.Cast<CodeTypeMember>(), c => c.Name == propertyName))
            {
                base.ClientProxyGenerator.LogError(string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.ClientCodeGen_NamingCollision_MemberAlreadyExists, new object[] { this._proxyClass.Name, propertyName }));
            }
            else
            {
                CodeTypeReference reference = CodeGenUtilities.GetTypeReference(entityType, base.ClientProxyGenerator, this._proxyClass);
                CodeTypeReference[] referenceArray = new CodeTypeReference[] { reference };
                CodeTypeReference reference2 = CodeGenUtilities.GetTypeReference("System.ServiceModel.DomainServices.Client.EntitySet", entityType.Namespace, false);
                reference2.TypeArguments.AddRange(referenceArray);
                CodePropertyReferenceExpression targetObject = new CodePropertyReferenceExpression(new CodeBaseReferenceExpression(), "EntityContainer");
                CodeMethodReferenceExpression method = new CodeMethodReferenceExpression(targetObject, "GetEntitySet", referenceArray);
                CodeMethodInvokeExpression expression = new CodeMethodInvokeExpression(method, new CodeExpression[0]);
                CodeMethodReturnStatement statement = new CodeMethodReturnStatement(expression);
                CodeMemberProperty property = new CodeMemberProperty {
                    Name = propertyName,
                    Type = reference2,
                    Attributes = MemberAttributes.Public | MemberAttributes.Final
                };
                property.GetStatements.Add(statement);
                this._proxyClass.Members.Add(property);
                property.Comments.AddRange(CodeGenUtilities.GenerateSummaryCodeComment(string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.CodeGen_EntitySet_Property_Summary_Comment, new object[] { entityType.Name, this._proxyClass.Name }), base.ClientProxyGenerator.IsCSharp));
            }
        }
    }
}

